﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Messaging;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace library
{
    class p2pRequest
    {
        internal Peer TargetPeer;

        internal Peer OriginPeer;

        internal p2pRequestHeader Header;

        byte[] Address;

        internal byte[] Data;

        static byte[] bytes_empty = new byte[0];

        static ManualResetEvent enqueueEvent = new ManualResetEvent(false);

        static Queue<p2pRequest> queue = new Queue<p2pRequest>();

        DateTime createTime;

        bool Expired()
        {
            return DateTime.Now.Subtract(createTime).TotalMilliseconds > Parameters.response_timeout;
        }

        p2pRequest(
            Peer originPeer,
            Peer targetPeer = null,
            byte[] address = null,
            byte[] data = null,
            RequestCommand command = RequestCommand.Data,
            Peer returnPeer = null)
        {
            OriginPeer = originPeer;

            TargetPeer = targetPeer;

            Address = address;

            Data = data;

            Header = new p2pRequestHeader(command, returnPeer);

            createTime = DateTime.Now;
        }

        internal p2pRequest(Peer originPeer, byte[] data)
        {
            OriginPeer = originPeer;

            TargetPeer = originPeer;

            Data = data;

            Header = new p2pRequestHeader(data);
        }

        #region Thread Refresh

        internal static void Start()
        {
            Thread thread = new Thread(Refresh);

            thread.Start();
        }

        internal static void Stop()
        {
            enqueueEvent.Set();

            Client.Stats.belowMaxSentEvent.Set();
        }

        static void Refresh()
        {
            while (!Client.Stop)
            {
                enqueueEvent.WaitOne();

                if (Client.Stop)
                    break;

                Client.Stats.belowMaxSentEvent.WaitOne();

                if (Client.Stop)
                    break;

                Dequeue();
            }
        }

        #endregion

        internal static void Dequeue()
        {
            lock (queue)
            {
                while (queue.Any())
                {
                    p2pRequest request = queue.Dequeue();

                    while (DateTime.Now.Subtract(request.createTime).TotalMilliseconds > Parameters.time_out && queue.Any())
                        request = queue.Dequeue();

                    if (DateTime.Now.Subtract(request.createTime).TotalMilliseconds > Parameters.time_out)
                        break;

                    if (Client.Stop)
                        break;

                    Send(
                        request.OriginPeer,
                        request.TargetPeer,
                        request.Address,
                        request.Data,
                        request.Header.Command,
                        request.Header.Peer);
                }

                enqueueEvent.Reset();
            }
        }

        static internal void Enqueue(
            Peer originPeer,
            Peer targetPeer = null,
            byte[] address = null,
            byte[] data = null,
            RequestCommand command = RequestCommand.Data,
            Peer returnPeer = null) //todo: renomear pra headerPeer
        {
            p2pRequest req = new p2pRequest(
                originPeer,
                targetPeer,
                address,
                data,
                command,
                returnPeer);

            lock (queue)
                queue.Enqueue(req);

            enqueueEvent.Set();
        }

        static internal bool Send(
            Peer originPeer = null,
            Peer targetPeer = null,
            byte[] address = null,
            byte[] data = null,
            RequestCommand command = RequestCommand.Data,
            Peer returnPeer = null,
            bool ignoreDeadEnd = true,
            bool wait = true)
        {
            if (originPeer == null)
                originPeer = Client.LocalPeer;

            if (targetPeer == null)
            {
                byte[] search_address = command == RequestCommand.Metadata ? Utils.ReadBytes(address) : address;

                targetPeer = Peers.GetPeer(
                    closestToAddress: search_address,
                    excludeOriginAddress: search_address,
                    excludeOriginPeer: new[] { originPeer, targetPeer, returnPeer },
                    ignoreDeadEnd: ignoreDeadEnd,
                    command: command,
                    wait: wait);

                if (targetPeer == null)
                    return false;

                if (Client.Stop)
                    return false;
            }

            ThreadSend(

                targetPeer.EndPoint,

                new p2pRequestHeader(command, command == RequestCommand.Peer? targetPeer : returnPeer).ToBytes().

                Concat(address ?? bytes_empty).

                Concat(data ?? bytes_empty).ToArray());

            return true;
        }

        static void ThreadSend(IPEndPoint remoteEndPoint, byte[] data)
        {
            UdpClient u = new UdpClient();

            u.Client.ExclusiveAddressUse = false;

            u.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

            u.Client.Bind(new IPEndPoint(IPAddress.Any, Client.P2pPort));

            Log.Write(">>>    " + remoteEndPoint.Port + "    " + Utils.Points(data.Take(30)));

            Client.Stats.Sent.Add(data.Length);

            u.Send(data, data.Length, remoteEndPoint);
        }

    }
}
